:: How to get SDL: click https://starcatcher.us/TPT/MingwLibraries.zip to download
:: SDL means "Simple DirectMedia Layer"
@echo off
:: chcp 437 >nul & graftabl 936 >nul
md tmp 2>nul
more +35 %0 > tmp\%~n0.cpp
if not "%1"=="debug" (
set coptimize=-O3 -march=i686 -msse -msse2 -ffast-math -D_SDL
set cxxoptimize=-Wno-invalid-offsetof -msse -msse2 -O3 -ftree-vectorize -funsafe-math-optimizations -ffast-math -fomit-frame-pointer -funsafe-loop-optimizations -D_SDL
) else (
set coptimize=-Wall -g -DDEBUG -D_SDL
set cxxoptimize=-Wno-invalid-offsetof -msse -msse2 -Wall -g -DDEBUG -D_SDL
)
gcc -c -o tmp\bitop.o bitop.c %coptimize%
fasm lua-proc-asm.asm tmp\lua-proc-asm.o >nul
g++ -c -o tmp\event.o event.cpp %cxxoptimize%
g++ -c -o tmp\strop.o strop.cpp %cxxoptimize%
g++ -c -o tmp\rng32.o rng32.cpp %cxxoptimize% -std=gnu++11
g++ -c -o tmp\common-func.o common\func.cpp %cxxoptimize%
g++ -c -o tmp\conwin.o lua-conwin.cpp %cxxoptimize%
g++ -c -o tmp\lua-attrib.o lua-attrib.cpp %cxxoptimize%
g++ -c -o tmp\lua-conxlat.o lua-conxlat.cpp %cxxoptimize%
g++ -c -o tmp\lua-font.o lua-font.cpp %cxxoptimize%
g++ -c -o tmp\lua-int32.o lua-int32.cpp %cxxoptimize%
g++ -c -o tmp\cmdargs.o cmdargs.cpp %cxxoptimize%
g++ -c -o tmp\%~n0.o tmp\%~n0.cpp %cxxoptimize% -std=gnu++11
g++ tmp\*.o -o %~n0.exe -Wl,-Bstatic -static-libgcc  -lluajit -lmingw32 -lm -lSDLmain -lSDL -lwinmm -mwindows -Wno-unused-result
:: rd /s /q tmp
if not "%1"=="debug" (
strip %~n0.exe
)
%~n0.exe
pause > nul
goto :eof

#include <iostream>
#include <string>
#include <cstdlib>
// #include <stack>
#include <queue>
#include <map>
#include <SDL/SDL.h>
#include <csetjmp>
#include "../console-common.h"
#include "../lua-font.h"
#include "../ascii-font.cpp"
#include "../lua-conwin.h"
#include "../lua-attrib.h"
#include "../lua-debug-funcs.h"
#include "../lua-extra-attr.h"
#include "../lua-fn-aliases.h"
#include "../lua-render-ex.h"

#ifdef DEBUG
# ifdef _WIN64
#  pragma message ("default CS register is 0x0033")
# elif defined(_WIN32)
#  pragma message ("default CS register is 0x0023")
# endif
#endif
#if defined(_WIN32) && !defined(_WIN64) && defined(__GNUC__) && defined(X86)
# define USE_GCC_WIN32
#endif

void read_cmd_arg(lua_State * L, char * arg, bool * k, int * f, int * p, int c, char ** v);
void sdl_decrement_timeout();

#define cmd_preset_color __static_cmd_preset_color

// Using CMD's color <attr>
static int cmd_preset_color[16] = { 
	0x000000, 0x000080, 0x008000, 0x008080, 0x800000, 0x800080, 0x808000, 0xC0C0C0,
	0x808080, 0x0000FF, 0x00FF00, 0x00FFFF, 0xFF0000, 0xFF00FF, 0xFFFF00, 0xFFFFFF,
};

extern "C" int luaopen_bit(lua_State *L);
int luacon_extend_str_api(lua_State *L);
int luarng_init_(lua_State *L);

int	input_chars = 0, input_pos = 0, first_input_line = 0, lua_cursor = 1,
	tmp_input_pos_0 = 0, attr_stack_base_pointer, luacon_tls_idx;
	
#ifdef __GNUC__
__thread jmp_buf * luacon_tls_panic;
#endif

// lua_State * primary_state, * lua_subthread_state;

bool fullscreened = false,
     fullscreen_wait = false,
     enable_fullscreen_shortcut = true;

#define altflags luaevent_altkey_flags
#define text_buf luacon_text_buffer

int altflags = (
	ALT_CAN_ENABLE |
	ALT_ECHO_INPUT
);

SDL_Surface *sdl_scrn;

// std::stack <int>
std::vector <int> attr_stack;
std::vector <int*> luacon_thread_handles;

std::queue <std::string> lua_proc_func_queue;

int32_t *text_buf = NULL, *luacon_mask_table_0 = NULL;
std::vector <int32_t*> semigraphics_bufs;

char *input_buffer = NULL, *input_buffer2 = NULL;
fontdata_t *ascii_font_ptr = NULL;

HANDLE MainThreadHandle = (HANDLE)0;
_MY_HANDLE_STATUS ScriptThreadHandle
#ifdef __GNUC__
__attribute__ ((aligned (16)))	// must be aligned
#endif
= {NULL, 0};

int	curr_fg = DEFAULT_FGCOLOR, curr_bg = DEFAULT_BGCOLOR,
	gfx_index = 0, tmp_gfx_index = 0, tmp_gfx_index_2 = 0;

std::string luacon_lastCode_v;
#define lastCode luacon_lastCode_v // it's variable, not a function

int luacon_writeTableError(lua_State* L)
{
	return luaL_error(L, "attempt to update a read-only table");
}

void sdl_scrn_setcursor(unsigned int * vid)
{
	bool curs = altflags & ALT_HAS_CURSOR;
	if ((gfx_index == 0 || curs) && lua_cursor)
	{
		lua_cursor++;
		if (lua_cursor > 60) lua_cursor = 1;
		int c, r, i, j;
		if (curs)
			r = ALT_STATUS_LINE, c = tmp_input_pos_0 + 1;
		else
			c = input_pos, r = first_input_line + c / XCHRS,
			c %= XCHRS;
		if (lua_cursor > 30)
		{
			for (i = 0; i < CHRW; i++)
				for (j = CHRH-(CHRH/4); j < CHRH; j++)
					vid[(c*CHRW+i)+(XRES)*(r*CHRH+j)] = ~vid[(c*CHRW+i)+(XRES)*(r*CHRH+j)];
		}
	}
}

void sdl_blit(int x, int y, int w, int h, unsigned int *src, int pitch)
{
	unsigned *dst;
	int i, j;
	if (SDL_MUSTLOCK(sdl_scrn))
		if (SDL_LockSurface(sdl_scrn)<0)
			return;
	dst = (unsigned *)sdl_scrn->pixels+y*sdl_scrn->pitch/4+x;
	for (j = 0; j < h; j++)
	{
		for (i = 0; i < w; i++)
			dst[i] = src[i];
		dst += sdl_scrn->pitch/4;
		src += pitch/4;
	}
	if (SDL_MUSTLOCK(sdl_scrn))
		SDL_UnlockSurface(sdl_scrn);
	SDL_UpdateRect(sdl_scrn,0,0,0,0);
}

void reverse_buffer_int32 (int32_t * buf, int len)
{
	int half = len / 2, tmp, i;
	for (i = 0; i < half; i++)
	{
		tmp = buf[i];
		buf[i] = buf[len-i-1];
		buf[len-i-1] = tmp;
	}
#ifdef DEBUG
	std::cout << &buf[0]-text_buf << ", " << &buf[len]-text_buf << std::endl;
	std::cout << i << std::endl;
#endif
}

#define luaL_identity lua_gettop
#define TAB_SIZE 8

int sdl_stdout (const char* str, int len = -1)
{
	int tmp_fgc = curr_fg, k = first_input_line;
	int tmp, i, j, upshift, chr;
	bool wrap = false, revvid = false;
#if FONT_NUM_CHAR_BITS >= 9
	int tmpoff = 0x100;
#endif
	if (len == -1)
		len = strlen(str);

	for (i = 0, j = 0; i < len; i++)
	{
		switch (str[i])
		{
		case '\t': j += TAB_SIZE - j % TAB_SIZE; break;
		case '\n': j = XCHRS; break;
		case '\x0F':
			i += 3; if (i >= len) goto end_of_loop;
			curr_fg = ((str[i-2] & 0xFF) << 16) | ((str[i-1] & 0xFF) << 8) | (str[i] & 0xFF);
			break;
		case '\x0E':
			curr_fg = tmp_fgc;
			break;
#if FONT_NUM_CHAR_BITS >= 9
		case '\x0B':
			if (++i >= len) goto end_of_loop;
			chr = tmpoff | (str[i] & 0xFF);
			goto extra_char_0;
			break;
#endif
		case '\x11':
			tmp = (unsigned char)(str[++i]);

			switch (tmp >> 3)
			{
			case 5:
#if FONT_NUM_CHAR_BITS >= 10
				if (tmp < 0x2A)
				{
					tmpoff += (tmp == 0x28) ? 0x100 : -0x100;
					tmpoff %= FONT_NUM_CHARS - 0x100;
					while (tmpoff < 0x100) tmpoff += FONT_NUM_CHARS - 0x100;
				}
				else if (tmp == '/')
				{
					tmpoff = 0x100;		
				}
#endif
				break;
			case 6: case  7: // 0x30-0x39 = number part
				if (tmp <= '9') curr_fg = cmd_preset_color[tmp - '0'];
				break;
			case 8: case 12: // 0x41-0x5A, 0x61-0x7A = alphabetic part
				{
					char _tmp = tmp & 7; // e.g. 0x41 = 'A'
					if (_tmp != 0 && _tmp != 7) curr_fg = cmd_preset_color[_tmp + 9];
				}
				break;
			case 16: case 17: case 18: case 19: // 0x80-0x9F: used as "blockic" mode
				chr = tmp & 0x1F;
				goto extra_char_0;
			}
			break;
		case '\x12': // reverse video
			revvid = !revvid;
			break;
		default:
			chr = str[i] & 0xFF;
// #if FONT_NUM_CHAR_BITS >= 9
		extra_char_0:
// #endif
			if (revvid)
				drawchr_inv(text_buf, j, k, chr);
			else
				drawchr(text_buf, j, k, chr);
			j++;
		}
		if (j >= XCHRS)
		{
			k++; j = 0;
			if (k >= YCHRS) wrap = true, k -= YCHRS;
			if (wrap) clear_line(text_buf, k);
		}
	}

end_of_loop:
	first_input_line = (k + 1) % YCHRS;
	curr_fg = tmp_fgc;

	if (first_input_line != 0 && !wrap) return 0;

	if (first_input_line != YCHRS-1)
	{
		upshift = first_input_line + 1;
		reverse_buffer_int32 (text_buf, (SSKIP/4) *XCHRS*upshift);
		reverse_buffer_int32 (text_buf+ (SSKIP/4) *XCHRS*upshift, (SSKIP/4)*XCHRS*(YCHRS-upshift));
		reverse_buffer_int32 (text_buf, (SSKIP/4) *XCHRS*YCHRS);
		first_input_line = YCHRS-1;
		// std::cout << upshift << std::endl;
	}
	
	clear_line(text_buf, YCHRS-1);
	return 0;
}

#define sdl_stderr sdl_stdout

static int __lua_restart_prog (lua_State * l)
{	
#if defined(X86) && defined(__GNUC__)
	__sync_fetch_and_or(&altflags, ALT_RESTART_PROG);
#else
	EnterCriticalSection(&luacon_critical_0);
	altflags |= ALT_RESTART_PROG;
	LeaveCriticalSection(&luacon_critical_0);
#endif
	return 0;
}

int luaL_optint_gfx_i(lua_State * L, int n)
{
	int i = luaL_optint(L, n, gfx_index);
	if (IS_FREED_BUFF(i, semigraphics_bufs))
		i = gfx_index;
	return i;
}

const char* lua_tostring_ext (lua_State * L, int n)
{
	switch (lua_type(L, n))
	{
		case LUA_TNUMBER:
		case LUA_TSTRING:
			return lua_tostring(L, n);
		case LUA_TBOOLEAN:
			return (lua_toboolean(L, n) ? "true" : "false");
			break;
		case LUA_TNIL:
			return "nil";
		default:
			lua_pushfstring(L, "%s: %p", luaL_typename(L, n), lua_topointer(L, n));
			const char* s = lua_tostring(L, -1);
			lua_pop(L, 1);
			return s;
	}
}

int luacolor_cmd2rgb (lua_State * l)
{
	int cmd_attr = luaL_checkint(l, 1);
	lua_pushinteger(l, cmd_preset_color[cmd_attr & 0xF]);
	lua_pushinteger(l, cmd_preset_color[(cmd_attr >> 4) & 0xF]);
	return 2;
}

int luacolor_photwl2rgb (lua_State * L)
{
	uint32_t x = luaL_checkinteger(L, 1);
	static const uint64_t m = 0x4332322132212110LL;
	int r, g, b, i;
	for (i = 0; i < 12; i += 4)
		r += (m >> 4 * ((x >> (i + 18)) & 0xF)) & 0xF,
		g += (m >> 4 * ((x >> (i +  9)) & 0xF)) & 0xF,
		b += (m >> 4 * ((x >> (i +  0)) & 0xF)) & 0xF;
	i = 624 / (r + g + b + 1);
	r *= i; (r > 255) && (r = 255);
	g *= i; (g > 255) && (g = 255);
	b *= i; (b > 255) && (b = 255);
	lua_pushinteger(L, (r<<16) | (g<<8) | b);
	return 1;
}

int luacolor_rgb (lua_State * l)
{
	int red   = luaL_checkint(l, 1);
	int green = luaL_checkint(l, 2);
	int blue  = luaL_checkint(l, 3);
	clamp_int8(red)
	clamp_int8(green)
	clamp_int8(blue)
	lua_pushinteger(l, (red<<16)|(green<<8)|blue);
	return 1;
}

int lua_alloc_subcon (lua_State * l)
{
	lua_pushboolean(l, AllocConsole());
	SetConsoleTitle("Lua subconsole");
	return 1;
}

int lua_free_subcon (lua_State * l)
{
	lua_pushboolean(l, FreeConsole());
	return 1;
}

int luacon_output (lua_State * l)
{
	int args = lua_gettop(l);
	int tmp_fgc, tmp_bgc, tmp_arg;
	const char *tmp_str;
	size_t tmp_len;

	if (args >= 1)
	{
		if (args >= 2)
		{
			tmp_fgc = curr_fg;
			tmp_bgc = curr_bg;
			tmp_arg = luaL_checkinteger(l, 2);
			curr_bg = args >= 3 ? luaL_checkinteger(l, 3) : tmp_bgc;
			curr_fg = tmp_arg;
		}
		if (lua_type(l, 1) == LUA_TSTRING)
			tmp_str = lua_tolstring(l, 1, &tmp_len), sdl_stdout(tmp_str, tmp_len);
		else
			sdl_stdout(lua_tostring_ext(l, 1));
		if (args >= 2)
		{
			curr_fg = tmp_fgc;
			curr_bg = tmp_bgc;
		}
	}
	return 0;
}

int lua_write_subcon (lua_State * l)
{
	HANDLE HConsole = GetStdHandle(STD_OUTPUT_HANDLE);
	const char* str = lua_tostring_ext(l, 1);
	WriteConsole(HConsole, str, strlen(str), NULL, NULL);
	return 0;
}

#define luacon_to_bound(begin, end, max)\
	if (begin < 0) begin = 0;\
	if (end > (max)) end = (max);

static int __luacon_drawchr_general (lua_State * l, void (*fn)(int32_t*, int, int, int))
{
	int args = lua_gettop(l);
	int arg1, // character
		arg2, // position x
		arg3, // position y
		arg4, // number of times to write
		arg5, // window page
		endx, i;
	if (args >= 3)
		luaL_va_tointegers (l, 2, &arg2, &arg3, NULL);
	else
		return luaL_error(l, "Must be least 3 arguments");
	arg4 = luaL_optint(l, 4, 1);
	arg5 = luaL_optint_gfx_i(l, 5);
	switch (lua_type(l, 1))
	{
	case LUA_TNUMBER:
		arg1 = lua_tointeger(l, 1);
		break;
	case LUA_TSTRING:
		arg1 = lua_tostring(l, 1)[0] & 0xFF;
		break;
	default:
		return luaL_error(l, "argument #1 must be number or string");
		break;
	}
	int32_t* buf = semigraphics_bufs[arg5];
	if (arg4 < 0) arg4 = 0;
	if (arg3 >= 0 && arg3 < YCHRS)
	{
		endx = arg2 + arg4;
		luacon_to_bound(arg2, endx, XCHRS);
		for (i = arg2; i < endx; i++)
			fn(buf, i, arg3, arg1);
	}
	return 0;
}

int luacon_drawchr (lua_State * L)
{
	return __luacon_drawchr_general(L, drawchr);
}

int luacon_drawchr_inv (lua_State * L)
{
	return __luacon_drawchr_general(L, drawchr_inv);
}

int luacon_readchr (lua_State * l)
{
	int args = lua_gettop(l);
	int x, y, f, p, ra, beg;

	if (args >= 2)
		x = lua_tointeger(l, 1),
		y = lua_tointeger(l, 2);
	else
		return luaL_error(l, "Must be least 2 arguments");
	f = luaL_optint(l, 3, 0);
	p = luaL_optint_gfx_i(l, 4);
	ra = (f & FLAG_READ_ATTR) ? 3 : 1;
	beg = (SSKIP/4) * (y*(XCHRS)+x);

	for (int i = 0; i < ra; i++)
		if (IN_CONSOLE(x, y))
			lua_pushinteger(l, semigraphics_bufs[p][beg + i]);
		else
			lua_pushnil(l);
	return ra;
}

int luacon_cls (lua_State * L)
{
	int arg1 = luaL_optint_gfx_i(L, 1);
	clear_line(semigraphics_bufs[arg1], 0, YCHRS);
	if (arg1 == 0)
	{
		first_input_line = 0;
	}
	return 0;
}

int luacon_cls_ex (lua_State * L)
{
	luacon_cls(L);
	if (lua_toboolean(L, 2))
		luacon_scrn_clear_blink(L);
	return 0;
}

int luacon_getcondata (lua_State * l)
{
	int p = luaL_optint_gfx_i(l, 5), x, y, w, h;
	int *(args[]) = {&x, &y, &w, &h, NULL},
	    argvalue[] = {0, 0, XCHRS, YCHRS};
	luaL_va_optints(l, 1, args, argvalue);
	
	if (w < 0 || h < 0 || w > XCHRS || h > YCHRS)
		return luaL_error(l, "Size out of range");

	int32_t	*src = semigraphics_bufs[p],
			*dst = (int32_t*)lua_newuserdata (l, w * h * SSKIP + 4); // canvas size + 4 byte

	*dst = (w & 0xFFFF) | ((h & 0xFFFF) << 16); dst++;

	int x1 = x, y1 = y, x2 = x + w, y2 = y + h, sz;
	luacon_to_bound (x1, x2, XCHRS);
	luacon_to_bound (y1, y2, YCHRS);
	x1 *= SSKIP/4, x2 *= SSKIP/4, x *= SSKIP/4, sz = x2-x1;

	for (int i = y1; i < y2; i++)
		memcpy(&dst[(i-y)*w*(SSKIP/4)+(x1-x)], &src[i*XCHRS*(SSKIP/4)+x1], sz<0?0:sz);
	return 1;
}

int luacon_setcondata (lua_State * l)
{
	// int args = lua_gettop(l);
	if (lua_type(l, 1) != LUA_TUSERDATA)
		return luaL_error(l, "argument #1 must be userdata");
	int len = luaL_getn(l, 1);
	int x = luaL_optint(l, 2, 0);
	int y = luaL_optint(l, 3, 0);
	int p = luaL_optint_gfx_i(l, 4);
	int32_t	*src = (int32_t*)lua_touserdata(l, 1),
			*dst = semigraphics_bufs[p];

	int w = *src & 0xFFFF, h = (*src >> 16) & 0xFFFF;
	src++;

	if (len != w * h * SSKIP + 4 || w > XCHRS || h > YCHRS)
		return luaL_error(l, "set console data error");
	
	int x1 = x, y1 = y, x2 = x + w, y2 = y + h, sz;
	luacon_to_bound (x1, x2, XCHRS);
	luacon_to_bound (y1, y2, YCHRS);
	x1 *= SSKIP/4, x2 *= SSKIP/4, x *= SSKIP/4, sz = x2-x1;

	for (int i = y1; i < y2; i++)
		memcpy(&dst[i*XCHRS*(SSKIP/4)+x1], &src[(i-y)*w*(SSKIP/4)+(x1-x)], sz<0?0:sz);
	return 0;
}

#undef luacon_to_bound

#define MAX_LEN (XCHRS-24)

int sdl_poll(bool* entered)
{
	if (altflags & (ALT_ERR_RUN2 | ALT_RESTART_PROG)) return 1;
	EnterCriticalSection(&luacon_critical_0);
	static char cmd [MAX_LEN + 1];
	SDL_Event event;
	int sdl_key = 0, unicode = 0, return_value = 0;
	bool tmp_entered = false;
	while (SDL_PollEvent(&event))
	{
		switch (event.type)
		{
		case SDL_KEYDOWN:
			sdl_key = event.key.keysym.sym;
			unicode = event.key.keysym.unicode;
			altflags &= ~ALT_PRESSING;
			if (sdl_key == SDLK_LALT || sdl_key == SDLK_RALT)
				altflags |= ALT_PRESSING;
			if (sdl_key == SDLK_KP_ENTER || sdl_key == SDLK_RETURN)
				tmp_entered = true;
			*entered = console_event_process(sdl_key, unicode, event.key.keysym.mod, text_buf, tmp_entered, &return_value, cmd);
			if (tmp_entered)
				goto retn;
			break;
		case SDL_KEYUP:
			sdl_key = event.key.keysym.sym;
			SDL_console_key_release(sdl_key, event.key.keysym.mod);
			if (sdl_key == SDLK_LALT || sdl_key == SDLK_RALT)
			{
				char f = altflags;
				if (f & ALT_PRESSING)
				{
					altflags &= ~ALT_PRESSING;
					if (f & ALT_CAN_ENABLE && !luacon_altmenu_suspend_count)
						altflags ^= ALT_ENABLED,
						console_altmode_call(f & ALT_ENABLED, cmd); // false = enabling, true = disabling
				}
			}
			break;
		case SDL_QUIT:
			return_value = 1;
			goto retn;
		}
	}
retn:
	if (altflags & ALT_ENABLED)
	{
		SDL_add_status_bar (semigraphics_bufs[gfx_index]);
	}
	LeaveCriticalSection(&luacon_critical_0);
	return return_value;
}

int luacon_wait (lua_State * l)
{
	int time = luaL_checkint(l, 1);
	SDL_Delay(time);
	return 0;
}

static bool input_script_flag = true;

int lua_proc_push_func (lua_State * L, char* add_str, HANDLE evt)
{
	lua_proc_func_queue.push(std::string(add_str));
	if (evt != NULL)
		SetEvent(evt);
	return 0;
}

#define luaL_custom_struct_0(name) \
	struct name { \
		lua_State *L;\
		_MY_HANDLE_STATUS *h;\
		bool *input_script_flag;\
		HANDLE sync;\
		DWORD sync_tid;\
	}

// static std::map <intptr_t, intptr_t> __luacon_panic_throw_bufs; // <lua_State*, jmp_buf*>

#ifdef USE_GCC_WIN32
# define MY_SEH_BEGIN(f)	\
	do {	\
		long * _seh_head = __builtin_alloca (12);\
		__asm__ __volatile__ ("\
			mov {%%fs:0, %%eax|eax, fs:[0]}\n\
			mov {%%eax, (%0)|[%0], eax}\n\
			lea {%1, %%eax|eax, %1}\n\
			mov {%%eax, 4(%0)|[%0+4], eax}\n\
			mov {%2, 8(%0)|[%0+8], %2}\n\
			mov {%0, %%fs:0|fs:[0], %0}"\
			:: "r"(_seh_head), "m"(f), "r"(L)\
			: "eax", "memory"\
		);
# define MY_SEH_END	\
		__asm__ __volatile__ (\
			"mov {%0, %%fs:0|fs:[0], %0}"\
			:: "r"(*_seh_head)\
			: "eax", "memory"\
		);	\
	}
# define USE_GCC_WIN32
#endif
#if defined(MY_SEH_BEGIN) && defined(USE_FASM)
__attribute__((regparm(1)))
int __luacon_process_pcall (lua_State * L) __asm__(".luacon_process_pcall");
__attribute__((regparm(3)))
void __luacon_swap64_asm (void*, void*, void*) __asm__(".luacon_swap64");
__attribute__((noinline, regparm(1), force_align_arg_pointer))
int __luacon_pcall_multret_0 (lua_State * L) __asm__(".luacon_pcall_multret.0");
int __luacon_pcall_resetstkoflw (void) __asm__(".luacon_pcall_resetstkoflw");

int __luacon_pcall_multret_0 (lua_State * L)
{
	return lua_pcall(L, 0, LUA_MULTRET, 0);
}

int __luacon_pcall_resetstkoflw (void)
{
	::EnterCriticalSection(&luacon_critlib_lock_0);

	static bool init = false;
	static FARPROC proc = (FARPROC)NULL;
	int (*f)(void);

	if (!init)
	{
		HMODULE m = ::LoadLibrary("msvcrt.dll");
		if (m)
			proc = GetProcAddress(m, "_resetstkoflw");
		init = true;
	}

	(FARPROC&)f = proc;

	::LeaveCriticalSection(&luacon_critlib_lock_0);
	
	if (f != NULL)
		return f();
	return 0;
}

#else
# ifndef MY_SEH_BEGIN
#  define MY_SEH_BEGIN(f)	{
#  define MY_SEH_END	}
# endif

__declspec(noinline)
int __luacon_process_pcall_exception (void)
{
	return 1;
}

int __luacon_process_pcall (lua_State * L)
{
	MY_SEH_BEGIN(__luacon_process_pcall_exception)
		return lua_pcall(L, 0, LUA_MULTRET, 0);
	MY_SEH_END
}
#endif

int lua_process (void *_st)
{
	luaL_custom_struct_0(st0) *st = (st0 *)_st;

	int level;
	lua_State *L = st->L;
	
	jmp_buf panicbuf;

	memset(&panicbuf, 0, sizeof(jmp_buf)); // clears signature	

#ifdef __GNUC__
	luacon_tls_panic = &panicbuf;
#else
	if (!TlsSetValue(luacon_tls_idx, &panicbuf))
		abort();
#endif

	if (setjmp(panicbuf))
	{
#ifdef __GNUC__
		__sync_fetch_and_or(&altflags, ALT_ERR_RUN2);
#else
		EnterCriticalSection(&luacon_critical_0);
		altflags |= ALT_ERR_RUN2;
		LeaveCriticalSection(&luacon_critical_0);
#endif
		CloseHandle(st->sync);
		return 0;
	}
	for (;;)
	{
		for (;;)
		{
			EnterCriticalSection(&luacon_critical_0);
			if (!lua_proc_func_queue.empty())
				break;
			LeaveCriticalSection(&luacon_critical_0);
			WaitForSingleObject(st->sync, INFINITE);
		}
		std::string add_str = lua_proc_func_queue.front();
		lua_proc_func_queue.pop();
		LeaveCriticalSection(&luacon_critical_0);
		
		int save_top = lua_gettop(L);
		
		if (!*(st->input_script_flag))
		{
			int retc = luaL_loadfile(L, add_str.c_str());
			if (!retc)
				retc = __luacon_process_pcall(L);
			*(st->input_script_flag) = true;
		}
		else
		{
			if (lastCode.length())
				lastCode += "\n";
			lastCode += add_str;
			std::string tmp = "return " + lastCode;
			luaL_loadbuffer(L, tmp.c_str(), tmp.length(), "@console");
			if (lua_type(L, -1) != LUA_TFUNCTION)
			{
				lua_pop(L, 1);
				luaL_loadbuffer(L, lastCode.c_str(), lastCode.length(), "@console");
			}
			if (lua_type(L, -1) != LUA_TFUNCTION)
			{
				std::string err = lua_tostring_ext(L, -1);
				if (err.find("near '<eof>'") != err.npos) //the idea stolen from lua-5.1.5/lua.c
					sdl_stdout("...", 3);
				else
				{
					sdl_stderr(err.c_str());
					lastCode = "";
				}
			}
			else
			{
				lastCode = "";
				int retc = __luacon_process_pcall(L);
				if (retc)
				{
					luacon_reg_clearupmem(L);
					sdl_stderr(lua_tostring_ext(L, -1));
				}
				else
				{
					int top = lua_gettop(L); size_t l; const char * s;
					for (level = save_top + 1; level <= top; level++)
						if (lua_type(L, level) == LUA_TSTRING)
							s = lua_tolstring(L, level, &l), sdl_stdout(s, l);
						else
							sdl_stdout(lua_tostring_ext(L, level));
				}
			}
		}

		lua_settop(L, save_top);
	}
	// lua_close(L);
	return 0;
}

#define STACK_SIZE (1 << 11)

static int __luacon_read_handles (lua_State* L)
{
	int i = luaL_optinteger(L, 2, 0), v = 0;
	if (i >= 0 && i < (int)luacon_thread_handles.size())
		v = *(luacon_thread_handles[i]);
	lua_pushinteger(L, v);
	return 1;
}

static void __luacon_render_process (int * first_tick, unsigned * vid)
{
	EnterCriticalSection(&luacon_critical_0);
	if (fullscreen_wait)
	{
		SDL_ShowCursor(!fullscreened);
		SDL_SetVideoMode(XRES, YRES, 32, SDL_SWSURFACE | (fullscreened ? SDL_FULLSCREEN : 0));
		fullscreen_wait = false;
	}
	LeaveCriticalSection(&luacon_critical_0);
	*first_tick += 20;
	int last_tick = SDL_GetTicks();
	int delay = *first_tick - last_tick;
	if (delay > 0)
		SDL_Delay(delay);
	else
	{
		SwitchToThread();
		*first_tick = last_tick;
	}
	EnterCriticalSection(&luacon_critical_0);
	sdl_translate_map(semigraphics_bufs[gfx_index], vid, altflags);
	sdl_scrn_setcursor(vid);
	sdl_decrement_timeout();
	sdl_blit(0, 0, XRES, YRES, vid, XRES*4);
	LeaveCriticalSection(&luacon_critical_0);
}

static int __luacon_panic_throw (lua_State* L)
{
	void * buf;
#ifdef __GNUC__
	buf = luacon_tls_panic;
#else
	buf = TlsGetValue(luacon_tls_idx);
	if (buf == NULL) // (::GetLastError() != ERROR_SUCCESS)
		abort();
#endif
	longjmp(*(jmp_buf*)buf, 1);
}

intptr_t luacon_stateRegTable[2 * REG_MAX_LUA_STATES];

int main (int argc, char** argv)
{

#ifndef __GNUC__

	int tls_idx = TlsAlloc();
	if (tls_idx == TLS_OUT_OF_INDEXES)
		exit(1);

	luacon_tls_idx = tls_idx;

#endif
	
	int i, exit_status = 0;

	text_buf = (int32_t*)malloc(XCHRS*YCHRS*SSKIP);
	SDL_curr_status = (char*)malloc(XCHRS + (1<<7));

	for (i = 0; i < XCHRS*YCHRS*(SSKIP/4); i++)
		text_buf[i] = 0;

	for (i = 0; i < REG_MAX_LUA_STATES; i++)
	{
		luacon_stateRegTable[2*i] = (intptr_t)NULL;
	}

	luacon_mask_table_0 = (int32_t*)malloc(4*(XCHRS*YCHRS*2+1));
	luacon_mask_table_0[0] = -1;
	for (i = 0; i < XCHRS*YCHRS; i++)
		luacon_mask_table_0[2*i+1] = -1,
		luacon_mask_table_0[2*i+2] = 0;

	const static int attr_init[4] = {4, STACK_SIZE, DEFAULT_FGCOLOR, DEFAULT_BGCOLOR};

	attr_stack.reserve(STACK_SIZE);
	for (i = 0; i < 4; i++)
		attr_stack.push_back(attr_init[i]);
	attr_stack_base_pointer = attr_stack.size();
	attr_stack.resize(STACK_SIZE);

	semigraphics_bufs.reserve(1 << 9);
	semigraphics_bufs.push_back(text_buf);
	
	unsigned * vid = (unsigned*)calloc(XRES*YRES, sizeof(unsigned));
	
	if (SDL_Init(SDL_INIT_VIDEO)<0)
	{
		fprintf(stderr, "Initializing SDL: %s\n", SDL_GetError());
		exit(1);
	}
	atexit(SDL_Quit);
	sdl_scrn = SDL_SetVideoMode(XRES, YRES, 32, SDL_SWSURFACE);
	if (!sdl_scrn)
	{
		fprintf(stderr, "Creating window: %s\n", SDL_GetError());
		exit(1);
	}

	SDL_EnableKeyRepeat(SDL_DEFAULT_REPEAT_DELAY, SDL_DEFAULT_REPEAT_INTERVAL);
	SDL_EnableUNICODE(1);
	
	SDL_WM_SetCaption( "Lua console", NULL );

	char* chr_buffer = (char*)malloc(XCHRS * (YCHRS-1) + 1);
	input_buffer = chr_buffer;
	char* chr_buffer2 = (char*)malloc(XCHRS * (YCHRS-1) + 1);
	input_buffer2 = chr_buffer2;
	ascii_font_ptr = (fontdata_t *)calloc(FONT_NUM_CHARS, sizeof(intptr_t));
	
	for (i = 0; i < 95; i++)
		ascii_font_ptr[i+32] = (fontdata_t)ascii_font[i];
	
	bool is_quit;

	lua_State *L = luacon_regnewstate();
	if (L == NULL)
		exit(1);
		
	luacon_initCriticals();

	luaL_openlibs(L);
	lua_atpanic(L, __luacon_panic_throw);
	
	lua_pushcfunction(L, luacon_output);
	lua_setglobal(L, "print");

	const static struct luaL_Reg console_lua_api [] = {
		{"get_data", luacon_getcondata},
		{"put_data", luacon_setcondata},

		{"draw_char", luacon_drawchr},
		{"draw_char_inv", luacon_drawchr_inv},
		{"draw_char_decode", luacon_drawchr_patt_decode},
		{"read_char", luacon_readchr},
		{"draw_line", LuaConsoleGraphics::drawline},
		{"draw_rect", LuaConsoleGraphics::drawrect},
		{"fill_rect", LuaConsoleGraphics::fillrect},
		{"clear_rect", LuaConsoleGraphics::clearrect},
		{"draw_string", LuaConsoleGraphics::drawstr},
		{"draw_str_v", LuaConsoleGraphics::drawstr_vertical},
		{"clear", luacon_cls},
		{"clear_ex", luacon_cls_ex},

		{"set_blink_area", luacon_scrn_set_blink_a},
		{"set_blink_decode", luacon_scrn_set_blink_dec},
		{"clear_blink", luacon_scrn_clear_blink},

		{"delay", luacon_wait},
		{"rgb", luacolor_rgb},
		{"cmd2rgb", luacolor_cmd2rgb},
		{"photwl2rgb", luacolor_photwl2rgb},
		{"icebp", luadbg_icebp},
		{"ident", luaL_identity},
		{"open_cmd", luadbg_open_cmd_shell},
		{"restart", __lua_restart_prog},
		
		{"pop_message", luacon_popmessage},

		// {"new_thread", luacon_new_thread}, // needs pthread's pthread_create or windows' CreateThread or C++11's std::thread
		{NULL, NULL}
	};

#define ARGEXT(x) {#x,x},
#define ARGEXT_RGBI(x) ARGEXT(x##_RED) ARGEXT(x##_GREEN) ARGEXT(x##_BLUE) ARGEXT(x##_INTENSITY)

	const static struct { const char* name; int value; } console_lua_api_ints[] = {
		{"LUA_CONSOLE", 0}, {"LUA_WINDOW", 1}, {"EMPTY_WINDOW", -1},
		ARGEXT(XRES) ARGEXT(YRES) ARGEXT(XCHRS) ARGEXT(YCHRS) ARGEXT(FLAG_RESET_STACK) ARGEXT(FLAG_READ_ATTR) 
		ARGEXT_RGBI(FOREGROUND) ARGEXT_RGBI(BACKGROUND) {NULL, 0},
		{"WIDTH", CHRW}, {"HEIGHT", CHRH}, {"FLIP_X", FONT_FLIP_X}, {"FLIP_Y", FONT_FLIP_Y}, {"ROTATE", FONT_ROTATE},
		{"BITS", FONT_NUM_CHAR_BITS}, {"MASK", (1 << FONT_NUM_CHAR_BITS) - 1}, {NULL, 0},
	};
	
	luaL_register(L, "console", console_lua_api);
	SET_GLOBAL_ALIAS(L, "console", "con");

#ifdef __GNUC__
	lua_pushliteral(L, MTOS(__GNUC__) "." MTOS(__GNUC_MINOR__));
	lua_setfield(L, -2, "GNUC");
#endif

	const static struct luaL_Reg console_additional_api [] = {
		{"alloc", luacon_allocchr},
		{"alloc_mult", luacon_alloc_multchr},
		{"alloc_cont", luacon_allocchr_continuous},
		{"free", luacon_freechr},
		{"check", luacon_checkchr},
		{"fill_rect", luacon_font_fill},
		{"new_data", luacon_font_newdata},
		{"get_data", luacon_font_getdata},
		{"set_data", luacon_font_setdata},
		{"to_data", luacon_to_font_data},
		{"equal", luacon_font_is_equal},
		{"band", luacon_font_bitw_and},
		{"bor", luacon_font_bitw_or},
		{"bnot", luacon_font_bitw_not},
		{"bxor", luacon_font_bitw_xor},
		{"xshift", luacon_font_shift_x},
		{"yshift", luacon_font_shift_y},
		{"flip", luacon_font_flip},
		{"file", luacon_font_fileop},
		{"character", NULL},
		{"open", lua_alloc_subcon},
		{"close", lua_free_subcon},
		{"print", lua_write_subcon},
		{"subconsole", NULL},
		{"alloc", luacon_alloc_scrn},
		{"free", luacon_free_scrn},
		{"count", luacon_window_cnt},
		{"capacity", luacon_window_cpct},
		{"recalc", luacon_window_recalc},
		{"switch", luacon_switch_window},
		{"check", luacon_check_window},
		{"file", luacon_window_fileop},
		{"fullscreen", luacon_set_fullscreen},
		{"next_window", luacon_next_window},
		{"window", NULL},
		{"suspend", luacon_altmenu_suspend},
		{"resume", luacon_altmenu_resume},
		{"altmenu", NULL},
		{NULL, NULL}
	};
	const static char* console_api_aliases[] = {
		"attribute", "attr", "character", "char", "character", "font", "subconsole", "subcon", "window", "win", "random", "rand",
		"draw_string", "draw_str", NULL };

	luaopen_con_attribute_api_(L);
	luaopen_con_int32_api_(L); lua_setfield(L, -2, "int32");
	luaL_register_ext(L, console_additional_api);
	luarng_init_(L);
	luaL_set_aliases(L, console_api_aliases);

	luaopen_bit(L);
	luacon_extend_str_api(L);

	lua_settop(L, 0); // 清理 Lua 堆栈

	int first_tick = SDL_GetTicks();

	HANDLE sync_evt = CreateEvent(NULL, FALSE, FALSE, NULL);
	luaL_custom_struct_0(st0) luacon_struct_0 = {L, &ScriptThreadHandle, &input_script_flag, sync_evt, 0};

	HANDLE _th = GetCurrentThread();  // 线程伪句柄, 通常是 -2
	HANDLE _ph = GetCurrentProcess(); // 进程伪句柄, 通常是 -1

	DuplicateHandle(_ph, _th, _ph, &MainThreadHandle, 0, FALSE, DUPLICATE_SAME_ACCESS);

	SetThreadPriority(_th, 1);

	ScriptThreadHandle.h = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)lua_process, &luacon_struct_0, CREATE_SUSPENDED, &(luacon_struct_0.sync_tid));
	if (!ScriptThreadHandle.h)
	{
		exit_status = 1;
		goto brk_1;
	}

	luacon_thread_handles.push_back((int*)&MainThreadHandle);
	luacon_thread_handles.push_back((int*)&ScriptThreadHandle.h);

	lua_getglobal(L, "console");
	lua_newtable(L); // 普通表
	lua_newtable(L); // 元表
	SET_CFUNCTION(L, "__newindex", luacon_writeTableError);
	SET_CFUNCTION(L, "__index", __luacon_read_handles);
	lua_setmetatable(L, -2);
	lua_setfield(L, -2, "handles"); // 加载句柄列表 
	lua_pushstring(L, "https://gitee.com/git123hub/luajit-test");
	lua_setfield(L, -2, "SOURCE_CODE");
	{
	int i = luaL_va_setfield_ints(L, console_lua_api_ints);
	lua_getfield(L, -1, "character");
	luaL_va_setfield_ints(L, &console_lua_api_ints[i]);
	}
	lua_pop(L, 2); // 清理 Lua 堆栈

	for (i = 0; i < LUA_SUBSTATES_COUNT; i++)
		luacon_substates[i] = NULL;

	{
	char* filename_ptr = NULL;
	for (i = 1; i < argc; i++)
	{
		if (argv[i][0] == '-')
			read_cmd_arg(L, argv[i] + 1, &fullscreened, &altflags, &i, argc, argv);
		else if (filename_ptr == NULL)
			filename_ptr = argv[i];
	}

	ResumeThread(ScriptThreadHandle.h);

	bool entered = false;
	if (filename_ptr)
	{
		altflags &= ~ALT_ECHO_INPUT;
		input_script_flag = false;
		lua_proc_push_func(L, filename_ptr, sync_evt);
		do {
			is_quit = sdl_poll(&entered);
			entered = false;
			SwitchToThread();
			__luacon_render_process (&first_tick, vid);
		} while (!is_quit);
	}
	else
	{
		std::string lastCode = "";
		do {
			is_quit = sdl_poll(&entered);
			if (entered)
			{
				entered = false, input_chars = 0, input_pos = 0;
				lua_proc_push_func(L, chr_buffer, sync_evt);
			}
			SwitchToThread();
			__luacon_render_process (&first_tick, vid);
		} while (!is_quit);
	}
	if (altflags & ALT_ERR_RUN2) {
		gfx_index = 0, lua_cursor = 0, console_blue_screen(L, text_buf);
		for (;;)
		{
			SDL_Event event;
			while (SDL_PollEvent(&event)) if (event.type == SDL_QUIT) goto brk_1;
			__luacon_render_process(&first_tick, vid);
		}
	}
	}
brk_1:
	luaevent_cleanup_handles(L);
	free(chr_buffer);
	free(chr_buffer2);
	free(ascii_font_ptr);
	free(sdl_blinking_flags_0);
	free(luacon_mask_table_0);

	if (altflags & ALT_RESTART_PROG)
	{
		STARTUPINFO si;  
		PROCESS_INFORMATION pi;
		memset(&si, 0, sizeof(si));  
		memset(&pi, 0, sizeof(pi));  
		CreateProcess(NULL, GetCommandLine(), NULL, NULL, false, 0, NULL, NULL, &si, &pi);
	}
	return exit_status;
}
